#include "io/vsNetTasks.h"

#include <QHostAddress>
#include <QFile>

#include <assert.h>


const int c_max_request_string_length = 999;
const int c_max_headers               = 15; // max header readline calls for a socket


vsNetHeader::vsNetHeader(const QStringList &strings)
{
    foreach (QString s, strings)
        parseHeader(s);
}

vsNetHeader::vsNetHeader(QTcpSocket *s)
{
    if (s != 0 && s->canReadLine())
    {
        // read request header --------------------------------------------------------
        request = QString(s->readLine(c_max_request_string_length));
        parseRequest();

        int          peerPort = s->peerPort();
        QHostAddress peerAddress = s->peerAddress();
        QString peerStr;

        if (peerAddress.isLoopback())
            peerStr = "localhost";
        else if (peerAddress.isInSubnet(QHostAddress("192.168.0.0"), 16))
            peerStr = QString("intranet %1").arg(peerAddress.toString());
        else
        {
            peerStr = getIPServiceTemplate().arg(peerAddress.toString());

            if (peerPort > 0)
                peerStr += QString(":%1").arg(peerPort);
        }

        vsLog::i(QString("%1 -> %2").arg(peerStr).arg(request));
        int counter = 0;

        while (s->canReadLine() && counter < c_max_headers)
        {
            parseHeader(QString(s->readLine(c_max_request_string_length)));
            ++counter;
        }

        if (counter >= c_max_headers)
            vsLog::e("Achtung - header read limit reached.");
    }
}

void vsNetHeader::parseRequest()
{
    requestParams.clear();
    requestURL.clear();
    requestBody.clear();

    if (request.isEmpty())
        return;

    request = request.remove('\r').remove('\n');
    QStringList parts = request.split(' ');

    // type
    if (parts.first().isEmpty())
        return;

    if      (parts[0] == "GET")     type = vsTypes::http_get;
    else if (parts[0] == "HEAD")    type = vsTypes::http_head;
    else if (parts[0] == "VS")      type = vsTypes::p2p;
    else
        vsLog::e("Unsupported request type desu.");

    // body
    if (parts.length() == 1 || parts[1].isEmpty())
        return;

    QString reqbody = parts[1];

    // try to parse url
    int lastSlashPos = reqbody.lastIndexOf('/');

    if (lastSlashPos > -1)
    {
        ++lastSlashPos; // index to count
        requestURL = reqbody.left(lastSlashPos); // include the slash itself
        reqbody.remove(0, lastSlashPos);
    }

    if (reqbody.isEmpty())
        return;

    // try to parse params, if any
    int paramsStart = reqbody.indexOf('?');

    if (paramsStart > -1)
    {
        int paramSymbolCount = reqbody.length() - paramsStart - 1;
        QString paramsString = reqbody.right(paramSymbolCount);
        reqbody.chop(paramSymbolCount + 1); // including symbol itself

        if (!paramsString.isEmpty())
        {
            QStringList params = paramsString.split('&');

            foreach (QString pairStr, params)
            {
                int delim = pairStr.indexOf('=');

                if (delim > 0) // found and not first
                    requestParams[pairStr.left(delim)] = pairStr.right(pairStr.length() - delim - 1);
                else
                    requestParams[pairStr.remove('=')] = "";
            }
        }
    }

    requestBody = reqbody; // finally, rest is main request part
}

void vsNetHeader::parseHeader(QString header)
{
    header.remove('\r').remove('\n');

    if (header.isEmpty())
        return;

    int delimPos = header.indexOf(": ");

    if (delimPos > 0) // found and not first
        headers[header.left(delimPos)] = header.right(header.length() - delimPos - 2); // excluding delimiter itself
}

QStringList vsNetHeader::dump() const
{
    QStringList result;

    if (response.isEmpty())
        vsLog::i("Warning - dumping header with empty response");
    else
        result.append(response);

    for (QStringMap::const_iterator i = headers.constBegin(); i != headers.constEnd(); ++i)
        result.append(QString("%1: %2").arg(i.key()).arg(i.value()));

    return result;
}


QTcpSocket& operator<<(QTcpSocket &s, const vsNetHeader &h)
{
    QStringList strings(h.dump());

    if (!strings.isEmpty())
    {
        QTextStream ts(&s);
        ts.setAutoDetectUnicode(true);

        foreach(QString str, strings)
            ts << str << "\r\n";

        ts << "\r\n";
        ts.flush();
    }

    return s;
}


//-------------------------------------------------


vsTransferTask::vsTransferTask(QTcpSocket &s, qint64 estimatedByterate, QObject *parent)
    : QObject(parent), socket(s), byterate(estimatedByterate), blockReadingPos(0),
      keepWorking(true), shouldRequestMore(true)
{
    connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError()));
}

vsTransferTask::~vsTransferTask()
{
    disconnect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError()));

    if (keepWorking || !blocks.isEmpty())
    {
        vsLog::e("vsTransferTask is destroyed while should keep working");
        emit error();
    }
}

void vsTransferTask::addDataBlock(const vsDataBlockHolder &b, bool lastOne)
{
    blocks.append(b);
    shouldRequestMore = !lastOne;
}

void vsTransferTask::transfer(qint64 maxBytes)
{
    //--------------------------------------------
    bool ok = keepWorking; // if not, socket may be already invalid

    if (ok && blocks.isEmpty())
    {
        if (shouldRequestMore)
            needMore();
        else
            doneTransferring();

        ok = false;
    }

    if (ok && !(socket.isValid() || socket.isWritable()))
    {
        vsLog::e(QString("%1 valid: %2 writable: %3").arg("Transfer task transferring: invalid socket state")
                 .arg(socket.isValid()).arg(socket.isWritable()));
        emit error();
        ok = false;
    }

    if (ok && socket.bytesToWrite() > 1*1024*1024)
        // there's too much buffered already, idling
        ok = false;

    vsDataBlockHolder *block = ok ? &blocks.first() : 0; // previous signals may've cleared all data

    if (ok)
    {
        if (block->length() <= 0 || blockReadingPos >= block->length())
        {
            blockReadingPos = 0;
            blocks.removeFirst();
            transfer(maxBytes);

            ok = false;
        }
    }

    if (!ok)
        return;
    //--------------------------------------------
    // socket is writable and there's a non-empty block that's not (completely) transferred yet

    if (maxBytes <= 0)
         maxBytes = LONG_LONG_MAX;

    qint64 availableToWrite = block->length() - blockReadingPos;

    if (maxBytes < availableToWrite) // there's limit
    {
        socket.write(&block->data()[blockReadingPos], maxBytes);
        blockReadingPos += maxBytes;

        // rest should be transferred in a second, need MOAR
        if (shouldRequestMore && blocks.first() != blocks.last())
            if (block->length() - blockReadingPos < byterate)
                mayNeedMore();
    }
    else
    {
        // write rest of block, drop it and start from beginning
        socket.write(&block->data()[blockReadingPos], availableToWrite);
        blockReadingPos = 0;
        blocks.removeFirst();

        transfer(maxBytes - availableToWrite);
    }
}


void vsTransferTask::onSocketError()
{
    vsLog::e("Transfer task is halted because of socket error: " + socket.errorString());
    stop();
    emit error();
}

void vsTransferTask::stop()
{
    if (keepWorking)
    {
        socket.flush();
        socket.close();

        blocks.clear();
        keepWorking = false;
        shouldRequestMore = false;
    }
}


void vsTransferTask::doneTransferring()
{
    vsLog::s("Transfer complete: " + socket.objectName());

    blocks.clear();
    keepWorking = false;
    shouldRequestMore = false;

    socket.flush(); // potentially, may freeze the system
    socket.close(); // TODO: marking socket for deletion later corrupts streaming of last batch :/

    emit transferComplete();
}

void vsTransferTask::mayNeedMore()
{
    emit requestMore();
}

void vsTransferTask::needMore()
{
    emit requestMore();
}


//-------------------------------------------------


vsFileServeTask::vsFileServeTask(QTcpSocket &s, const QString &path, qint64 offset, qint64 count, quint64 totalSize,
                                 QObject *parent)
    : vsTransferTask(s, 0, parent),
      fpath(path), foffset(offset), fcount(count), fread(0), fsize(totalSize), requested(0)
{
    assert(!path.isEmpty());
    assert(totalSize > 0);
    assert(offset >= 0);

    if (fcount < 1)
        fcount = totalSize - offset;

    firstIndex = 0;
    ranges = splitFileByteRange(foffset, fcount, fsize, firstIndex);

    assert(!ranges.isEmpty());

    currentIndex = firstIndex;
    lastIndex = firstIndex + ranges.length() - 1;

    connect(vsFileStreamer::instance(), SIGNAL(doneReading(int,vsDataBlockHolder)),
            this,                       SLOT(onFileRead(int,vsDataBlockHolder)));
    connect(vsFileStreamer::instance(), SIGNAL(error(int,vsTypes::fileError)),
            this,                       SLOT(onFileError(int, vsTypes::fileError)));

    vsFileStreamer::instance()->requestRead(requested, fpath, firstIndex);
}

vsFileServeTask::~vsFileServeTask()
{
    blocks.clear();
    keepWorking = false;
    shouldRequestMore = false;

    disconnect(vsFileStreamer::instance(), SIGNAL(doneReading(int,vsDataBlockHolder)),
               this,                       SLOT(onFileRead(int,vsDataBlockHolder)));
    disconnect(vsFileStreamer::instance(), SIGNAL(error(int,vsTypes::fileError)),
               this,                       SLOT(onFileError(int, vsTypes::fileError)));
}

bool vsFileServeTask::operator==(const vsFileServeTask &other) const
{
    return socket.peerAddress() == other.socket.peerAddress() &&
            fpath == other.fpath && foffset == other.foffset && fcount == other.fcount;
}


void vsFileServeTask::onFileRead(int id, vsDataBlockHolder dbh)
{
    if (requested > 0 && id == requested)
    {
        requested = 0;
        QPair<qint64,qint64> range = ranges[currentIndex - firstIndex];
        range.first -= currentIndex * c_file_part_size; // use relative offset
        fread += dbh.length() - range.first;

        addDataBlock(vsDataBlockHolder(dbh, range.first, range.second), currentIndex == lastIndex);
    }
}

void vsFileServeTask::onFileError(int id, vsTypes::fileError etype)
{
    vsLog::e("File Serve task: file error " + id);

    if (requested > 0 && requested == id)
    {
        requested = 0;
        vsLog::e(QString("Error %1 while reading %2").arg(etype).arg(fpath));
        stop();
        emit error(); // ragequit
    }
}

void vsFileServeTask::mayNeedMore()
{
    if (requested <= 0)
    {
        assert(fread < fcount);
        currentIndex++;
        vsFileStreamer::instance()->requestRead(requested, fpath, currentIndex);
    }
}

void vsFileServeTask::needMore()
{
    if (requested <= 0)
    {
        assert(fread < fcount);
        currentIndex++;
        vsFileStreamer::instance()->requestRead(requested, fpath, currentIndex);
    }
}

void vsFileServeTask::doneTransferring()
{
    vsLog::s("Transfer complete: " + socket.objectName());

    blocks.clear();
    keepWorking = false;
    shouldRequestMore = false;

    char ending[4] = "\0\r\n";
    socket.write(ending);

    socket.flush(); // potentially, may freeze the system
    socket.close(); // TODO: marking socket for deletion later corrupts streaming of last batch :/

    emit transferComplete();
}
